﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using MbUnit.Framework;
using Quail.Helpers;

namespace Quail
{
	// ReSharper disable InconsistentNaming
	/// <summary>
    /// Exposes methods and properties used to interact with a web page.
    /// </summary>
    public class WebPage : WebControlContainer, IWebPage
    {
        private List<YSODSearchItem> _ysodSearch;
        private string _ysodSearchEvalString;
        private static string _ysodSearchEvalStringForEveryone;
        private static readonly List<YSODSearchItem> YsodSearchForEveryone = new List<YSODSearchItem>();

        private MetaData _meta;
        private TestSession _session;

        /// <summary>
        /// Initializes a new instance of the <see cref="WebPage"/> class.
        /// </summary>
        /// <param name="session">The test session containing the WebPage</param>
        public WebPage(TestSession session)
            : base(null)
        {
            _session = session;
            AutoRetryYSOD = true;
        }

        /// <summary>
        /// Delegate for OnHasYSOD event
        /// </summary>
        /// <param name="html">the html of the page</param>
        /// <param name="retrycounter">the number of times the page has been reloaded so far</param>
        /// <param name="reloadpage">gets or sets a value indicating to reload the page</param>
        public delegate void OnHasYSODEvent(string html, int retrycounter, ref bool reloadpage);

        /// <summary>
        /// Called when the WaitForAJAX method completes
        /// </summary>
		public event OnWebPageCompleteEvent OnWaitForAJAXIsComplete;

        /// <summary>
        /// Called when the Sync() method completes successfully
        /// </summary>
		public event OnWebPageCompleteEvent OnSyncIsCompleteEvent;

        /// <summary>
        /// Called when the Sync() method fails
        /// </summary>
        public event OnFailedEvent OnSyncFailedEvent;

        /// <summary>
        /// Fired when a YSOD is found.  Modify the reloadpage parameter to force the page to reload instead of throwing an exception
        /// </summary>
        public event OnHasYSODEvent OnHasYSOD;

        /// <summary>
        /// Gets the current ActiveTask
        /// </summary>
        public override Task ActiveTask
        {
            get { return Browser.ActiveTask; }
        }

        /// <summary>
        /// Gets a value indicating whether this <see cref="WebPage"/> is visible.
        /// </summary>
        /// <value>
        /// <c>true</c> if visible; otherwise, <c>false</c>.
        /// </value>
        public override bool Visible
        {
            get
            {
                return Exists;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this <see cref="WebPage"/> is enabled.
        /// </summary>
        /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
        public override bool Enabled
        {
            get
            {
                return Exists;
            }
        }

        /// <summary>
        /// Gets the page.
        /// </summary>
        /// <value>
        /// The page.
        /// </value>
        public override WebPage Page
        {
            get
            {
                return this;
            }
        }

        /// <summary>
        /// Gets the current Browser object
        /// </summary>
        public override WebBrowser Browser
        {
            get
            {
                return _session.Browser as WebBrowser;
            }
        }

        /// <summary>
        /// Gets the selector for the page (empty string)
        /// </summary>
        public override string Selector
        {
            get
            {
                return string.Empty;
            }

            set
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Gets the entire HTML of the web page except for the surrounding HTML tags.  
        /// To search the Html for text, please use the FindInSource() method
        /// </summary>
        public string Html
        {
            get
            {
                var task = ActiveTask.BeginTask("WebPage.Html");
                try
                {
                    var result = Browser.DoQuick(() => Browser.SeleniumInterface.GetHtmlSource());
                    task.Finish();
                    return result;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets the text of the page
        /// </summary>
        public string InnerText
        {
            get
            {
                return GetControl("body").InnerText;
            }
        }

        /// <summary>
        /// Gets the URL of the web page
        /// </summary>
        public string Location
        {
            get
            {
                return Browser.Location;
            }

            set
            {
                Browser.Navigate(value);
            }
        }

        /// <summary>
        /// Gets the location URI.
        /// </summary>
        /// <value>The location URI.</value>
        public Uri LocationUri
        {
            get
            {
                return Browser.LocationUri;
            }

            set
            {
                Browser.Navigate(value.ToString());
            }
        }

        /// <summary>
        /// Gets the common meta data on the page
        /// </summary>
        public MetaData Meta
        {
            get
            {
                return _meta ?? (_meta = new MetaData(this));
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to automatically attempt to fix a YSOD (by reloading the page)
        /// </summary>
        public bool AutoRetryYSOD
        {
            get; set;
        }

        /// <summary>
        /// Gets the URL of the web page, or returns an error string if an exception occurs
        /// </summary>
        public string SafeLocation
        {
            get
            {
                try
                {
                    return Location;
                }
                catch (Exception e)
                {
                    return "Exception occurred getting location: " + e.Message;
                }
            }
        }

        /// <summary>
        /// Gets the title of the web page
        /// </summary>
        public string Title
        {
            get
            {
                var task = ActiveTask.BeginTask("WebPage.Title");
                try
                {
                    var result = Browser.DoQuick(() => Browser.SeleniumInterface.GetTitle());
                    task.Rollback();
                    return result;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether the page exists
        /// </summary>
        public override bool Exists
        {
            get
            {
                var task = ActiveTask.BeginTask(GetType() + ".Exists");
                try
                {
                    var result = ExistsNoLog;
                    task.Finish(result.ToString(CultureInfo.InvariantCulture));
                    return result;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether the page exists without performing any logging
        /// </summary>
        public bool ExistsNoLog
        {
            get
            {
                using (Browser.CacheLocation())
                {
                    return IsThisPage();
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether a new page is loaded and, therefore, Sync has something to wait for
        /// </summary>
        public bool IsNewPagePending
        {
            get
            {
                return Browser.DoQuick(() => Browser.DoCommand("getIsNewPageLoading") == "true");
            }
        }

        /// <summary>
        /// Gets a value indicating whether a New Page has loaded.
        /// </summary>
        public bool IsNewPageLoaded
        {
            get
            {
                return Browser.DoQuick(() => Browser.DoCommand("getIsNewPageLoaded") == "true");
            }
        }

        /// <summary>
        /// Gets a value indicating whether an AJAX asynchronous query is in progress
        /// </summary>
        public bool IsAJAXQueryExecuting
        {
            get
            {
                return Browser.DoQuick(() => Browser.DoCommand("getIsAJAXQueryExecuting") == "true");
            }
        }

        /// <summary>
        /// Gets a value indicating whether an alert() was shown
        /// </summary>
        public bool IsAlertPresent
        {
            get
            {
                var task = ActiveTask.BeginTask("WebPage.IsAlertPresent");
                try
                {
                    var b = Browser.DoQuick(() => Browser.SeleniumInterface.IsAlertPresent());

                    task.Finish();

                    return b;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether a confirm() was shown
        /// </summary>
        public bool IsConfirmationPresent
        {
            get
            {
                var task = ActiveTask.BeginTask("WebPage.IsConfirmationPresent");
                try
                {
                    var b = Browser.DoQuick(() => Browser.SeleniumInterface.IsConfirmationPresent());
                    task.Finish();
                    return b;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets a javascript eval() string that tells when a page has an exception, like a YSOD, 404 or other type of unexpected error page.
        /// </summary>
        protected string YsodSearchEvalString
        {
            get
            {
                var items = GetYSODSearchItems();

                if (items == YsodSearchForEveryone)
                {
                    // use the pre-created default
                    _ysodSearch = YsodSearchForEveryone;
                    _ysodSearchEvalString = _ysodSearchEvalStringForEveryone;
                }
                else
                {
                    // use the new list
                    _ysodSearch = items.ToList();
                    _ysodSearchEvalString = GetFindInSourceEvalString(_ysodSearch.Select(ysod => ysod.TriggerText.ToLower()).ToArray());
                }

                return _ysodSearchEvalString;
            }
        }

        /// <summary>
        /// Method returns a list of YSODSearchItem objects to use in the Sync() method.
        /// If you override this method and want to use the Quail built-in YSOD search items, be sure to return base.GetYSODSearchItems()
        /// </summary>
        /// <returns>An enumeration of YSODSearchItems</returns>
        protected virtual IEnumerable<YSODSearchItem> GetYSODSearchItems()
        {
            // check if the static eval search string is defined or not
            if (YsodSearchForEveryone.Count == 0)
            {
                // not defined, generate the string
                lock (YsodSearchForEveryone)
                {
                    if (YsodSearchForEveryone.Count == 0)
                    {
                        YsodSearchForEveryone.AddRange(YSODSearchItem.GenericYSODSearchItems());
                        _ysodSearchEvalStringForEveryone = GetFindInSourceEvalString(YsodSearchForEveryone.Select(ysod => ysod.TriggerText.ToLower()).ToArray());
                    }
                }
            }


            return YsodSearchForEveryone;
        }

        /// <summary>
        /// Checks if the page exists within the default timeout period. Throws a WebPageNotFoundException if the page does not Exist in the timeout.
        /// </summary>
        public void WaitExists()
        {
            WaitExists("WebPage.WaitExists: Expected page did not show up within default timeout.");
        }

        /// <summary>
        /// Checks if the page exists within the timeout period.  Throws a WebPageNotFoundException if the page does not Exist in the timeout.
        /// </summary>
        /// <param name="message">Message to log as part of the log</param>
        public void WaitExists(string message)
        {
            if (Exists)
            {
                WaitForAJAX();
                return;
            }

            var watch = Browser.GetStopwatch();
            while (!watch.Expired)
            {
                Browser.Sleep(100);
                if (Exists)
                {
                    WaitForAJAX();
                    return;
                }
            }

            MustExist(message);
        }

        /// <summary>
        /// Validates that all given links (A tags only) on the page work and do not cause YSOD
        /// </summary>
        public void ValidateLinks(List<WebLink> links)
        {
            var task = ActiveTask.BeginTask("ValidateLinks");
            try
            {
                int linkcount = 0;

                foreach (var link in links)
                {
                    var linkurl = link.HrefAbsolute;
                    linkcount++;
                    var linktask = ActiveTask.BeginTask("Clicking Link");
                    linktask.AddAttribute("Url", linkurl);
                    try
                    {
                        var page = Location;
                        link.Click();

                        if (Location != page)
                            Browser.Back(this);

                        linktask.Finish();
                    }
                    catch (Exception e)
                    {
                        linktask.Finish(e);
                        throw;
                    }
                }

                task.AddAttribute("LinkCount", linkcount.ToString(CultureInfo.InvariantCulture));

                task.Finish();
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }

        }

        /// <summary>
        /// Validates that all the links (A tags only) on the page work and do not cause YSOD
        /// </summary>
        public void ValidateLinks()
        {
            var task = ActiveTask.BeginTask("ValidateLinks");
            try
            {
                var activeurl = Location;
                var links = new List<WebLink>();
                var u = new Uri(activeurl);

                foreach (var link in GetLinks())
                {
                    if (!link.Visible)
                        continue;

                    var linkurl = link.HrefAbsolute;
                    if (linkurl.StartsWith("javascript:"))
                        continue; // skip javascript

                    var u2 = new Uri(linkurl);

                    if ((!string.IsNullOrEmpty(u2.Host) && string.Compare(u.Host, u2.Host, StringComparison.OrdinalIgnoreCase) != 0) ||
						string.Compare(linkurl, activeurl, StringComparison.OrdinalIgnoreCase) == 0)
                        continue;

                    links.Add(link);
                }

                ValidateLinks(links);
                task.Finish();
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }


        /// <summary>
        /// Verifies that links exists to the specified Urls
        /// </summary>
        /// <param name="urls">list of urls</param>
        public void ValidateLinksExist(params string[] urls)
        {
            var task = ActiveTask.BeginTask("ValidateLinksExist");
            try
            {
                foreach (var url in urls)
                {
                    var link = GetLink("a:regex(href," + url + ")");

                    if (!link.Exists)
                        throw new WebPageException(this, "Link to " + url + " was not found.");

                    task.AddLog("Link to " + url + " found.");
                }

                task.Finish();
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Clears all INPUT, SELECT controls on the page
        /// </summary>
        public virtual void BlankOutAllControlsOnPage()
        {
            GetControls<WebInput>(":text").ForEach(control => control.Clear());
            GetControls<WebInput>("textarea").ForEach(control => control.Clear());
            // GetControlList<WebRadioGroup>(":radio").ForEach(control => control.Clear());
            GetControls<WebCheckBox>(":checkbox").ForEach(control => control.Clear());
            GetControls<WebList>("select").ForEach(control =>
                                                       {
                                                           if(control.ItemCount>0)
                                                               control.Clear();
                                                       }
            );
        }

        /// <summary>
        /// Waits for the page to load within Browser.Timeout seconds.  It then searches for a YSOD and if one is found, it logs a status of ysodStatus and then throws an YSODFoundException
        /// </summary>
        /// <param name="ysodStatus">The log status code to use if a YSOD is found</param>
        public virtual void Sync(LogStatus ysodStatus)
        {
            Sync(YSODCheckTypes.CheckForYSOD, ysodStatus, true);
        }

        /// <summary>
        /// Waits for the page to load within Browser.Timeout seconds.  If checkForYSOD is true, it then searches for a YSOD and if one is found, it logs a status of FailYsod and then throws an YSODFoundException
        /// </summary>
        /// <param name="checkForYSOD">Set to True if you want to call FindYSOD()</param>
        public virtual void Sync(YSODCheckTypes checkForYSOD)
        {
            Sync(checkForYSOD, LogStatus.FailYsod, true);
        }

        /// <summary>
        /// Waits for the page to load within Browser.Timeout seconds.  It then searches for a YSOD and if one is found, it logs a status of FailYsod and then throws an YSODFoundException
        /// </summary>
        public virtual void Sync()
        {
            Sync(YSODCheckTypes.CheckForYSOD, LogStatus.FailYsod, true);
        }

        /// <summary>
        /// Waits for the page to load within Browser.Timeout seconds.  It then searches for a YSOD and if one is found, it logs a status of FailYsod and then throws an YSODFoundException
        /// </summary>
        /// <param name="pages">Optional list of web pages to sync against</param>
        public virtual void Sync(params WebPage[] pages)
        {
            Sync(YSODCheckTypes.CheckForYSOD, LogStatus.FailYsod, true, pages);
        }

        /// <summary>
        /// Waits for any page to load within Browser.Timeout seconds.  It then searches for a YSOD and if one is found, it logs a status of ysodStatus and then throws an YSODFoundException
        /// </summary>
        /// <param name="ysodStatus">The log status code to use if a YSOD is found</param>
        public void SyncAnyPage(LogStatus ysodStatus)
        {
            Sync(YSODCheckTypes.CheckForYSOD, ysodStatus, false);
        }

        /// <summary>
        /// Waits for any page to load within Browser.Timeout seconds.  If checkForYSOD is true, it then searches for a YSOD and if one is found, it logs a status of FailYsod and then throws an YSODFoundException
        /// </summary>
        /// <param name="checkForYSOD">Set to True if you want to call FindYSOD()</param>
        public void SyncAnyPage(YSODCheckTypes checkForYSOD)
        {
            Sync(checkForYSOD, LogStatus.FailYsod, false);
        }

        /// <summary>
        /// Waits for any page to load within Browser.Timeout seconds.  It then searches for a YSOD and if one is found, it logs a status of FailYsod and then throws an YSODFoundException
        /// </summary>
        public void SyncAnyPage()
        {
            Sync(YSODCheckTypes.CheckForYSOD, LogStatus.FailYsod, false);
        }

        /// <summary>
        /// Waits for an AJAX asynchronous query to finish executing
        /// </summary>
        /// <returns>True if it waited on AJAX</returns>
        public bool WaitForAJAX()
        {
            return WaitForAJAX(Browser.Timeout / 1000);
        }

        /// <summary>
        /// Waits for an AJAX asynchronous query to finish executing
        /// </summary>
        /// <param name="seconds">Number of seconds to wait for the AJAX call to complete</param>
        /// /// <returns>True if it waited on AJAX</returns>
        public bool WaitForAJAX(int seconds)
        {
            var task = ActiveTask.BeginTask("WaitForAJAX");
			try
			{
				if (!ShouldWaitForAJAX())
				{
					task.Rollback();
					return false;
				}

				var tout = seconds*1000;
				try
				{
									Browser.WaitForJavascriptToReturnTrue(
					"!selenium.getIsAJAXQueryExecuting()",
					"AJAX",
					tout,
					"Timed out waiting for AJAX to finish",
					true);
				}
				catch (WaitParameters.WaitTimeoutException)
				{
					// translate a WaitTimeoutExeption into a WebPageTimedOutException
					throw new WebPageTimedOutException(this, tout);
				}

				if (OnWaitForAJAXIsComplete != null)
					OnWaitForAJAXIsComplete(this);

				task.Finish();
				return true;
			}
			catch (Exception e)
			{
				task.Finish(e);
				throw;
			}
        }

		/// <summary>
        /// Checks to see if the web page should bother waiting for AJAX
        /// </summary>
        /// <returns>True if AJAX is executing, false otherwise</returns>
        protected virtual bool ShouldWaitForAJAX()
        {
            return IsAJAXQueryExecuting;
        }

        /// <summary>
        /// Looks for a YSOD, if one is found, it logs a FailYsod status and throws a YSODFoundException
        /// </summary>
        public void FindYSOD()
        {
            FindYSOD(LogStatus.FailYsod, null);
        }

        /// <summary>
        /// Looks for a YSOD, if one is found, it logs a FailYsod status with the supplied message and throws a YSODFoundException
        /// </summary>
        /// <param name="message">A message to add to the log if a YSOD is found</param>
        public void FindYSOD(string message)
        {
            FindYSOD(LogStatus.FailYsod, message);
        }

        /// <summary>
        /// Looks for a YSOD, if one is found, it logs a status specified by ysodStatus and throws a YSODFoundException
        /// </summary>
        /// <param name="ysodStatus">The status to use if a YSOD is found</param>
        public void FindYSOD(LogStatus ysodStatus)
        {
            FindYSOD(ysodStatus, null);
        }

        /// <summary>
        /// Looks for a YSOD, if one is found, it logs a status specified by ysodStatus along with the supplied message and throws a YSODFoundException
        /// </summary>
        /// <param name="ysodStatus">The status to use if a YSOD is found</param>
        /// <param name="message">A message to add to the log if a YSOD is found</param>
        public void FindYSOD(LogStatus ysodStatus, string message)
        {
            if (HasYSOD(ysodStatus, message))
                throw new YSODFoundException(this, Location);
        }

        /// <summary>
        /// Looks for a YSOD and if a YSOD is found, it logs a Warning.
        /// </summary>
        /// <returns>True if a YSOD is found.</returns>
        public bool HasYSOD()
        {
            return HasYSOD(LogStatus.Warning, null);
        }

        /// <summary>
        /// Looks for a YSOD and if a YSOD is found, it logs a status of ysodStatus.
        /// </summary>
        /// <param name="ysodStatus">The status to use if a YSOD is found</param>
        /// <returns>True if a YSOD is found.</returns>
        public bool HasYSOD(LogStatus ysodStatus)
        {
            return HasYSOD(ysodStatus, null);
        }

        /// <summary>
        /// Looks for a YSOD and if a YSOD is found, it logs a status of ysodStatus along with the supplied message.
        /// </summary>
        /// <param name="ysodStatus">The status to use if a YSOD is found</param>
        /// <param name="message">An additional message to log if a YSOD is found</param>
        /// <returns>True if a YSOD is found.</returns>
        public bool HasYSOD(LogStatus ysodStatus, string message)
        {
            Func<Task, bool>
                hasit
                    =
                    task =>
                        {
                            var tryagain = AutoRetryYSOD && ysodStatus.IsFailingStatus();
                            bool foundysod = false, recoverattempt = false;
                            string logmessage = string.Empty;

                            for (var retrycounter = 0;; retrycounter++)
                            {
								var html = Browser.DoQuick(() => Browser.GetEval(YsodSearchEvalString, true));

                                if (string.IsNullOrEmpty(html))
                                    break;

                                var htmllower = html.ToLower();

                                foreach (var ysod in _ysodSearch
                                    .Where(ysod => htmllower.Contains(ysod.TriggerTextLower)))
                                {
                                    logmessage = ysod.Message + " found";

                                    if (string.IsNullOrEmpty(message))
                                        logmessage += "\n" + html;
                                    else
                                        logmessage += ": " + message + "\n" + html;

                                    foundysod = true;
                                    break;
                                }

                                if (!foundysod)
                                    break;

                                if (OnHasYSOD != null)
                                    OnHasYSOD(html, retrycounter, ref tryagain);

                                ysodStatus = GetYSODStatus(html, ysodStatus, retrycounter, ref tryagain);

                                if (!tryagain)
                                    break;

                                // log the YSOD as warning
                                task.AddLog(LogStatus.Warning, logmessage);
                                task.AddLog(LogStatus.Information, "Attempting recovery from YSOD");
                                Browser.SeleniumInterface.Refresh();
                                SyncAnyPage(YSODCheckTypes.DontCheckForYSOD);
                                tryagain = false;
                                foundysod = false;
                                recoverattempt = true;
                            }

                            if (!foundysod)
                            {
                                if (recoverattempt)
                                {
                                    task.AddLog(LogStatus.Information, "Recovered from YSOD");
                                    task.Finish(LogStatus.Pass);
                                }
                                else
                                    task.Rollback();

                                return false;
                            }

                            // the YSOD persists, log using the ysodStatus
                            task.Finish(ysodStatus, logmessage);
                            return true;
                        };

            return ActiveTask.RunTask(hasit, false);
        }

        /// <summary>
        /// Gets the YSOD status to log when a YSOD occurs
        /// </summary>
        /// <param name="html">The HTML of the page</param>
        /// <param name="ysodStatus">The current ysodStatus that will be logged</param>
        /// <param name="retrycounter">The number of retries for this YSOD</param>
        /// <param name="tryagain">In/Out parameter to determine if a YSOD retry should occur</param>
        /// <returns>The new ysodStatus to use (you should return ysodStatus as a default)</returns>
        protected virtual LogStatus GetYSODStatus(string html, LogStatus ysodStatus, int retrycounter, ref bool tryagain)
        {
            return ysodStatus;
        }

        /// <summary>
        /// Returns the content of the last alert()
        /// </summary>
        /// <returns>The text of the last alert</returns>
        public string GetAlert()
        {
            var task = ActiveTask.BeginTask("WebPage.GetAlert");
            try
            {
                var result = Browser.DoQuick(() => Browser.SeleniumInterface.GetAlert());
                task.Finish(result);
                return result;
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Gets the confirmation text
        /// </summary>
        /// <returns>the confirm text</returns>
        public string GetConfirmation()
        {
            var task = ActiveTask.BeginTask("WebPage.GetConfirmation");
            try
            {
                var result = Browser.DoQuick(() => Browser.SeleniumInterface.GetConfirmation());
                task.Finish(result);
                return result;
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Indicates Selenium should click "Cancel" on the next confirm() popup.    Be sure to call GetConfirmation() after the confirm is clicked!
        /// </summary>
        public void ChooseCancelOnNextConfirmation()
        {
            var task = ActiveTask.BeginTask("WebPage.ChooseCancelOnNextConfirmation");
            try
            {
                Browser.SeleniumInterface.ChooseCancelOnNextConfirmation();
                task.Finish();
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Indicates Selenium should click "OK" on the next confirm() popup.  Be sure to call GetConfirmation() after the confirm is clicked!
        /// </summary>
        public void ChooseOkOnNextConfirmation()
        {
            var task = ActiveTask.BeginTask("WebPage.ChooseOkOnNextConfirmation");
            try
            {
                Browser.SeleniumInterface.ChooseOkOnNextConfirmation();
                task.Finish();
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Looks for text in the Html source of the page, if it is found, the html source is returned
        /// </summary>
        /// <param name="texttofind">Text to look for</param>
        /// <returns>Empty string if not found, otherwise the html source</returns>
        public string FindInSource(params string[] texttofind)
        {
            var task = ActiveTask.BeginTask("FindInSource");
            var ctr = 0;
            foreach (var s in texttofind)
            {
                task.AddAttribute("Text" + ctr, s);
                ctr++;
            }

            try
            {
                var result = Browser.DoQuick(() => Browser.GetEval(GetFindInSourceEvalString(texttofind), true));

                task.Finish(result.Length == 0 ? "Not found" : "Found");

                return result;
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Looks for text in a string
        /// </summary>
        /// <param name="texttofind">Text to look for</param>
        /// <param name="texttosearch">Text to search</param>
        /// <returns>Index of item in text to search</returns>
        public int FindInString(string texttofind, string texttosearch)
        {
            return texttosearch.IndexOf(texttofind, StringComparison.CurrentCultureIgnoreCase);
        }

        /// <summary>
        /// Throws a WebPageNotFoundException if the page does not exist
        /// </summary>
        public void MustExist()
        {
            MustExist("Expected page does not exist: " + GetType());
        }

        /// <summary>
        /// Throws a WebPageNotFoundException if the page does not exist
        /// </summary>
        /// <param name="message">The message to display if the page does not exist</param>
        public void MustExist(string message)
        {
            if (!Exists)
            {
                message += "  Actual page: " + Location;
                ActiveTask.AddLog(LogStatus.Fail, message);
                throw new WebPageNotFoundException(this, message);
            }
        }

        /// <summary>
        /// Call this method to just "make the page go away".  (must be implemented in sub-classes)
        /// </summary>
        public virtual void Dismiss()
        {
            throw new NotImplementedException("Dismiss must be implemented in sub-classes");
        }

        /// <summary>
        /// Performs actions on the page based on the options and optional parameters
        /// </summary>
        /// <param name="options">The type of excercise to perform</param>
        /// <param name="parms">Optional parameters for the page</param>
        public virtual void Exercise(ExerciseOptions options, params object[] parms)
        {
            throw new NotImplementedException("Exercise must be implemented in sub-classes");
        }

        /// <summary>
        /// If the page doesn't Exist, it attempts to Navigate to the page
        /// </summary>
        public void NavigateToThisPageIfNotExists()
        {
            if (Exists)
                Sync();
            else
                NavigateToThisPage();
        }

        /// <summary>
        /// Gets the QueryString collection for this page
        /// </summary>
        /// <returns>A NameValueCollection</returns>
        public NameValueCollection GetQueryString()
        {
            var url = new Uri(Location);
            return System.Web.HttpUtility.ParseQueryString(url.Query);
        }

        /// <summary>
        /// Gets a specific query string value from the Location for the page
        /// </summary>
        /// <param name="name">The query string parameter name</param>
        /// <returns>The value for the name</returns>
        public string GetQueryStringValue(string name)
        {
            var coll = GetQueryString();
            return coll.Get(name);
        }

        /// <summary>
        /// Gets a dictionary of <see cref="UrlParameter"/> objects based on the query string of the current location
        /// </summary>
        /// <returns>A dictionary with a case insensitive key of <see cref="UrlParameter"/> objects</returns>
        public Dictionary<string, UrlParameter> GetUrlParameters()
        {
            var url = new Uri(Location);
            var items = GetQueryString()
                .Keys
                .OfType<string>()
                .Select(x => new UrlParameter(url, x))
                .ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
            return items;
        }

        /// <summary>
        /// Gets a <see cref="UrlParameter"/> instance for the named query string parameter
        /// </summary>
        /// <param name="name">The query string parameter</param>
        /// <returns>A <see cref="UrlParameter"/> object</returns>
        public UrlParameter GetUrlParameter(string name)
        {
            var param = new UrlParameter(Location, name);
            return param;
        }
        
        /// <summary>
        /// Navigates to this page
        /// </summary>
        public virtual void NavigateToThisPage()
        {
            throw new NotImplementedException("NavigateToThisPage must be implemented in sub-classes");
        }

        /// <summary>
        /// Attempts to fill out the page with meaningful data
        /// </summary>
        public virtual void FillOut()
        {
            throw new NotImplementedException("FillOut must be implemented in sub-classes");
        }

		/// <summary>
		/// Attempts to check the page level validations
		/// </summary>
		public virtual void VerifyPageValidations()
		{
            throw new NotImplementedException("VerifyPageValidations must be implemented in sub-classes");
		}

        /// <summary>
        /// Verifies that the entity matches the contents of the page
        /// </summary>
        public virtual void VerifyEntity()
        {
            throw new NotImplementedException("VerifyEntity must be implemented in sub-classes");
        }

    	/// <summary>
        /// Returns True if this page matches the current Location (should be impemented in sub-classes)
        /// </summary>
        /// <returns>True if the current browser page is this page</returns>
        protected virtual bool IsThisPage()
        {
            return true;
        }

        /// <summary>
        /// Peforms the submit.  Doesn't care where it goes or if there are errors.
        /// </summary>
        /// <param name="attributes">The attributes.</param>
        public virtual void PeformSubmit(WebPageSubmitAttributes attributes)
        {
            var task = ActiveTask.BeginTask("PeformSubmit");
            try
            {
                if (attributes.EnterControls != null && attributes.EnterControls.Count > 0 && (RandomStuff.RandomNumber(0,10)< -1 || attributes.SubmitWithEnter) )
                {
                    var ctrl = attributes.EnterControls.RandomItem();
                    ctrl.Event.Click();
                    ctrl.Event.KeyPress(13);
                    task.AddLog(string.Format("Form submitted with enter event.  {0} picked", ctrl.MySelector));
                }
                else
                {
                    attributes.SubmitButton.Click(false);//do not do a sync on the click, the below sync will do it and pass through any attributes
                    task.AddLog("Form submitted with button click event");
                }

                if(attributes.PauseAfterSubmit.HasValue)
                {
                    Browser.Sleep(attributes.PauseAfterSubmit.Value);
                }

                SyncAnyPage(attributes.CheckForYSOD);

                task.Finish();
            }
            catch(Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Submits the form and checks the validation errors don't exist based on the WebPageSumitAttributes.ExpectsError property.
        /// If a list of controls is passed in
        /// </summary>
        /// <param name="attributes">The web page submit attributes.</param>
        /// <returns></returns>
        public virtual bool PerformSubmitAndVerifyErrorMessage(WebPageSubmitAttributes attributes)
        {
            var task = ActiveTask.BeginTask("PerformSubmitAndVerifyErrorMessage");
            try
            {
                if(!attributes.DoNotSubmitPage)
                {
                    PeformSubmit(attributes);

                    VerifyErrorMessage(attributes);

                    task.Finish();
                    return true;
                }

                task.AddAttribute("Submit","Not clicked");
                task.Finish();
                return false;
            }
            catch(Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Checks the validation errors don't exist based on the WebPageSumitAttributes.ExpectsError property.
        /// </summary>
        /// <param name="attributes">The attributes.</param>
        public virtual void VerifyErrorMessage(WebPageSubmitAttributes attributes)
        {
            var task = ActiveTask.BeginTask("VerifyErrorMessage");
            try
            {
				task.AddAttribute("ExpectsError", attributes.ExpectsError.ToString(CultureInfo.InvariantCulture));

                if (attributes.ExpectsError && !Exists)
                    throw new WebPageValidationErrorException(this,
                                                              "After submit, an error message was expected, but the page advanced to a new page.");
                if (Exists)
                {
                    var errorText = attributes.GetErrorTextMethod();
                    if (!string.IsNullOrEmpty(errorText))
                    {
                        if (!attributes.ExpectsError)
                            throw new WebPageValidationErrorException(this, string.Format("After submit, an unexpected error message was found: {0}",errorText));
                        task.AddLog(LogStatus.Information, errorText);
                    }

                    if (!attributes.ExpectsError && (!attributes.PageShouldExist && !attributes.IgnorePageExistence))
                        throw new WebPageValidationErrorException(this,
                                                                  "After submit, the page did not advance to a new page but the validationError was not found");
                }
                else
                {
                    if (attributes.PageShouldExist && !attributes.IgnorePageExistence)
                        throw new WebPageValidationErrorException(this,"After submit, the page advanced and should not have.");
                }

                task.Finish();
            }
            catch(Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Verifies a list of scenarios given an error message and an action
        /// </summary>
        /// <param name="attributes">attributes to use to find error message scenarios</param>
        public virtual void VerifyErrorMessageScenarios(WebPageErrorScenarioAttributes attributes)
        {
            var task = ActiveTask.BeginTask("VerifyErrorMessageScenarios");
            try
            {
                VerifyErrorMessageScenariosWhenAllFieldsAreBlank(attributes);

                foreach(var scenario in attributes.Scenarios)
                {
                    if(scenario.ErrorMethod == null)
                        continue;

                    if (!attributes.IsHtml5ValidationAllowed)
                        DeactivateHtml5Validation();

                    attributes.FillOutMethod();
                    scenario.ErrorMethod();
                    WaitForAJAX();
                    Browser.Sleep(500);
                    attributes.SubmitControl.Click();

                    if(attributes.PauseAfterSubmit.GetValueOrDefault(0) > 0)
                        Browser.Sleep(attributes.PauseAfterSubmit.GetValueOrDefault(0));
                    
                    if (!Exists)
                        Assert.Fail(
                            string.Format("Error message did not display, instead page advanced to {0}. Expected: {1}",
                                          Location, scenario.ErrorMessage));

                    VerifyValidationErrorMessageExists(attributes.GetErrorTextMethod(), scenario);
                }

                task.Finish();
            }
            catch(Exception e)
            {
                task.Finish(e);
                throw;
            }
            
        }

        /// <summary>
        /// Verifies the error message scenarios when all fields are blank.
        /// </summary>
        /// <param name="attributes">attributes to use to find error message scenarios</param>
        public virtual void VerifyErrorMessageScenariosWhenAllFieldsAreBlank(WebPageErrorScenarioAttributes attributes)
        {
            Sync();

            if (attributes.Scenarios.Any(s => s.ExpectedWhenAllFieldsAreBlank))
            {
                BlankOutAllControlsOnPage();
                if (attributes.ReadyBlankPageToSubmitMethod != null)
                    attributes.ReadyBlankPageToSubmitMethod();
                WaitForAJAX();
                attributes.SubmitControl.Click();
                Sync();

                if (attributes.PauseAfterSubmit.GetValueOrDefault(0) > 0)
                    Browser.Sleep(attributes.PauseAfterSubmit.GetValueOrDefault(0));

                var errorMessage = attributes.GetErrorTextMethod();
                foreach(var scenario in attributes.Scenarios)
                {
                    if (scenario.ExpectedWhenAllFieldsAreBlank)
                        VerifyValidationErrorMessageExists(errorMessage, scenario);
                }
            }
        }

        /// <summary>
        /// Verifies a validation message exists in a given web control, throws an exception if the error doesn't exist or isn't found
        /// </summary>
        /// <param name="errorMessage">the error message</param>
        /// <param name="scenario">the error scenario to check</param>
        public virtual void VerifyValidationErrorMessageExists(string errorMessage, ErrorMessageScenario scenario)
        {
            if (scenario.ErrorMessageRegEx != null)
                VerifyValidationErrorMessageExists(errorMessage, scenario.ErrorMessageRegEx);
            if(!string.IsNullOrEmpty(scenario.ErrorMessage))
                VerifyValidationErrorMessageExists(errorMessage, scenario.ErrorMessage);
        }

        /// <summary>
        /// Verifies a validation message exists in a given web control, throws an exception if the error doesn't exist or isn't found
        /// </summary>
        /// <param name="errorMessage">the error message</param>
        /// <param name="messageToFind">The message to find</param>
        public virtual void VerifyValidationErrorMessageExists(string errorMessage, string messageToFind)
        {
            var task = ActiveTask.BeginTask("VerifyValidationErrorMessageExists");
            task.AddAttribute("Message", messageToFind);
            try
            {
                var errorText = errorMessage;

                //task.Assert.IsTrue(LogStatus.Fail, errorText.Exists, "Validation error message does not exist.");
                task.Assert.Contains(LogStatus.Fail,
                    errorText,
                    messageToFind, 
                    "Validation error is found.");

                task.Finish();
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Verifies a validation message exists in a given web control, throws an exception if the error doesn't exist or isn't found
        /// </summary>
        /// <param name="errorMessage">the error message</param>
        /// <param name="messageToFind">The message to find</param>
        public virtual void VerifyValidationErrorMessageExists(string errorMessage, Regex messageToFind)
        {
            var task = ActiveTask.BeginTask("VerifyValidationErrorMessageExists");
            task.AddAttribute("MessageRegEx", messageToFind.ToString());
            try
            {
                var errorText = errorMessage;

                task.Assert.IsTrue(LogStatus.Fail,
                    messageToFind.IsMatch(errorText), 
                    "Validation error is not found.  Expected: " + messageToFind + ", got: " + errorText);

                task.Finish();
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Implementation of Sync to handle AJAX and page loads
        /// </summary>
        /// <param name="checkForYSOD">how to check for a YSOD</param>
        /// <param name="ysodStatus">The log status code to use if a YSOD is found</param>
        /// <param name="mustexist">if True, an exception is thrown is this.Exists == false</param>
        /// <param name="pages">Optional list of web pages to sync against</param>
        public virtual void Sync(YSODCheckTypes checkForYSOD, LogStatus ysodStatus, bool mustexist, params WebPage[] pages)
        {
            var task = ActiveTask.BeginTask("Sync");
			try
			{
				var tout = Browser.Timeout;
				try
				{
					Browser.WaitForJavascriptToReturnTrue(
						"!selenium.getIsAJAXQueryExecuting() && !selenium.getIsNewPageLoading()",
						"Sync",
						tout,
						"Timed out waiting for page to load",
						true);
				}
				catch (WaitParameters.WaitTimeoutException)
				{
					// translate a WaitTimeoutExeption into a WebPageTimedOutException
					throw new WebPageTimedOutException(this, tout);
				}

				Browser.ClearLocationCache();

				task.AddLog(LogStatus.Navigate);
				if (checkForYSOD == YSODCheckTypes.CheckForYSOD)
				{
					if (ysodStatus != LogStatus.Fail && ysodStatus != LogStatus.FailYsod)
						task.AddAttribute("findysodFlag", ysodStatus.ToString());
					FindYSOD(ysodStatus);
				}

				if (mustexist)
				{
					task.AddAttribute("mustexist", "True");
					if (pages.GetUpperBound(0) >= 0)
					{
						var found = false;
						foreach (var page in pages)
						{
							if (page.Exists)
							{
								found = true;
								break;
							}
						}

						if (!found)
						{
							throw new WebPageNotFoundException(this, "No expected pages were found!");
						}
					}
					else
					{
						MustExist();
					}
				}

				if (OnSyncIsCompleteEvent != null)
					OnSyncIsCompleteEvent(this);

				task.Finish();
			}
			catch (YSODFoundException y)
			{
				if (OnSyncFailedEvent != null)
					OnSyncFailedEvent(this, y);

				task.Finish(LogStatus.FailYsod, y);
				throw;
			}
			catch (Exception e)
			{
				if (OnSyncFailedEvent != null)
					OnSyncFailedEvent(this, e);

				task.Finish(e);
				throw;
			}
        }

        /// <summary>
        /// Deactivates HTML 5 validation on the current page
        /// </summary>
        public void DeactivateHtml5Validation()
        {
            const string javascript = @"jQuery('form').attr('novalidate','novalidate');";

            Browser.SeleniumInterface.RunScript(javascript);
        }


        /// <summary>
        /// Generates the string that is passed to getFindInsource javascript method
        /// </summary>
        /// <param name="texttofind">Text to look for</param>
        /// <returns>The string to pass to GetEval</returns>
        private static string GetFindInSourceEvalString(params string[] texttofind)
        {
            var cmd = "this.getFindInSource(";
            var first = true;

            foreach (var str in texttofind)
            {
                if (first)
                    first = false;
                else
                    cmd += ",";

                cmd += "\"" + str.Replace("\"", "\\\"") + "\"";
            }

            return cmd + ")";
        }

        /// <summary>
        /// Contains meta data fields common to all pages
        /// </summary>
        public class MetaData
        {            
            /// <summary>
            /// Initializes a new instance of the <see cref="MetaData"/> class.
            /// </summary>
            /// <param name="page">The web page.</param>
            public MetaData(WebPage page)
            {
                Page = page;
            }

            /// <summary>
            /// Gets the meta description on the page
            /// </summary>
            public string Description
            {
                get
                {
                    var control = Page.GetControl("meta[name='description']");
                    if (control.Exists)
                    {
                        return control.Attr(KnownAttribute.Content);
                    }
                    else return null;
                }
            }

            /// <summary>
            /// Gets the meta keywords on the page
            /// </summary>
            public string Keywords
            {
                get
                {
                    var control = Page.GetControl("meta[name='keywords']");
                    if (control.Exists)
                    {
                        return control.Attr(KnownAttribute.Content);
                    }
                    else return null;
                }
            }

            /// <summary>
            /// Gets a value indicating whether the robots noindex meta tag exists
            /// </summary>
            public bool NoRobots
            {
                get
                {
                    return Page.GetControl("meta[name='robots'][content='noindex']").Exists;
                }
            }

            /// <summary>
            /// Gets or sets the page we're on
            /// </summary>
            private WebPage Page
            {
                get; set;
            }
        }

        /// <summary>
        /// Exceptions when a page validation fails
        /// </summary>
        public class WebPageValidationErrorException : WebPageException
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="WebPageValidationErrorException"/> class.
            /// </summary>
            /// <param name="page">The page with the exception</param>
            /// <param name="message">The exception message</param>
            public WebPageValidationErrorException(WebPage page, string message)
                :base(page,message)
            {
            }
        }

        /// <summary>
        /// Attributes for doing Web Page Submittal
        /// </summary>
        public class WebPageSubmitAttributes
        {
            /// <summary>
            /// Gets or sets the get error text method.
            /// </summary>
            /// <value>The get error text method.</value>
            public Func<string> GetErrorTextMethod { get; set; }

            /// <summary>
            /// Gets or sets a value indicating whether an error is expected when calling this method.
            /// </summary>
            /// <value><c>true</c> if [expects error]; otherwise, <c>false</c>.</value>
            public bool ExpectsError { get; set; }

            /// <summary>
            /// Gets or sets a value indicating whether page is submitted with Enter key
            /// </summary>
            /// <value><c>true</c> if [submit with enter]; otherwise, <c>false</c>.</value>
            public bool SubmitWithEnter { get; set; }

            /// <summary>
            /// Gets or sets a value indicating whether the page should be submitted or not.
            /// </summary>
            /// <value><c>true</c> if [do not submit page]; otherwise, <c>false</c>.</value>
            public bool DoNotSubmitPage { get; set; }

            /// <summary>
            /// Gets or sets a value indicating whether the current page should exist after submit
            /// </summary>
            /// <value><c>true</c> if [page should exist]; otherwise, <c>false</c>.</value>
            public bool PageShouldExist { get; set; }

            /// <summary>
            /// Gets or sets a value indicating whether or not the submit should check for a YSOD
            /// </summary>
            public YSODCheckTypes CheckForYSOD { get; set; }

            /// <summary>
            /// Gets or sets the submit button.
            /// </summary>
            /// <value>The submit button.</value>
            public WebControl SubmitButton { get; set; }

            /// <summary>
            /// Gets or sets the list of enter controls.
            /// </summary>
            /// <value>The enter controls.</value>
            public List<WebControl> EnterControls { get; set; }

            /// <summary>
            /// Overrides ShouldPageExist and gives a third option to ignore page existence
            /// </summary>
            public bool IgnorePageExistence { get; set; }

            /// <summary>
            /// If greater than zero, will pause browser for number if miliseconds
            /// </summary>
            public int? PauseAfterSubmit { get; set; }
        }

        /// <summary>
        /// Attributes for executing error message scenarios
        /// </summary>
        public class WebPageErrorScenarioAttributes
        {
            /// <summary>
            /// Gets or sets the error message scenarios.
            /// </summary>
            /// <value>The scenarios.</value>
            public IEnumerable<ErrorMessageScenario> Scenarios { get; set; }

            /// <summary>
            /// Gets or sets the submit control.
            /// </summary>
            /// <value>The submit control.</value>
            public WebControl SubmitControl { get; set; }

            /// <summary>
            /// Gets or sets the error message control function.
            /// </summary>
            /// <value>The error message control function.</value>
            public Func<string> GetErrorTextMethod { get; set; }

            /// <summary>
            /// Gets or sets the fill out method.
            /// </summary>
            /// <value>The fill out method.</value>
            public Action FillOutMethod { get; set; }

            /// <summary>
            /// Gets or sets the method used to manipulate a blank page to allow submitting for validation.
            /// </summary>
            /// <value>The method to run after blanking the page.</value>
            public Action ReadyBlankPageToSubmitMethod { get; set; }

            /// <summary>
            /// Gets or sets a value indicating whether the scenario should deactivate
            /// HTML 5 validation
            /// </summary>
            public bool IsHtml5ValidationAllowed { get; set; }

            /// <summary>
            /// Amount of time to Pause after a submit.
            /// </summary>
            public int? PauseAfterSubmit { get; set; }
        }

        /// <summary>
        /// Wraps data needed during VerifyErrorMessageScenarios
        /// </summary>
        public class ErrorMessageScenario
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="ErrorMessageScenario"/> class.
            /// </summary>
            public ErrorMessageScenario()
            {
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="ErrorMessageScenario"/> class.
            /// </summary>
            /// <param name="errormessageRegex">The regular expresion for the error message.</param>
            /// <param name="method">The method to create the error.</param>
            /// <param name="expectedwhenallfieldsareblank">A flag that when true, this error message should show when all fields are blank</param>
            public ErrorMessageScenario(Regex errormessageRegex, Action method, bool expectedwhenallfieldsareblank)
            {
                ErrorMessageRegEx = errormessageRegex;
                ErrorMethod = method;
                ExpectedWhenAllFieldsAreBlank = expectedwhenallfieldsareblank;
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="ErrorMessageScenario"/> class.
            /// </summary>
            /// <param name="errormessage">The error message.</param>
            /// <param name="method">The method to create the error.</param>
            /// <param name="expectedwhenallfieldsareblank">A flag that when true, this error message should show when all fields are blank</param>
            public ErrorMessageScenario(string errormessage, Action method, bool expectedwhenallfieldsareblank)
            {
                ErrorMessage = errormessage;
                ErrorMethod = method;
                ExpectedWhenAllFieldsAreBlank = expectedwhenallfieldsareblank;
            }

            /// <summary>
            /// Gets or sets the expected error message
            /// </summary>
            public string ErrorMessage
            {
                get;
                set;
            }

            /// <summary>
            /// Gets or sets the expected error message regex
            /// </summary>
            public Regex ErrorMessageRegEx
            {
                get;
                set;
            }

            /// <summary>
            /// Gets or sets the action to create the error message
            /// </summary>
            public Action ErrorMethod
            {
                get;
                set;
            }

            /// <summary>
            /// Gets or sets a value indicating whether the error message should show when all fields are blank.
            /// </summary>
            public bool ExpectedWhenAllFieldsAreBlank
            {
                get;
                set;
            }
        }
    }
	// ReSharper restore InconsistentNaming
}
